#include "utils.hpp"

#include "../adsorb/adsorb.hpp"
#include "../axis/axis.hpp"
#include "../state/state.hpp"

namespace qbe::utils {

template <typename T, typename... Args>
entt::entity copy_impl(entt::registry& reg, entt::entity e, layout::LayoutCmd& cmd, qlib::Vec2d translate) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    auto copy_ptr = (*ptr)->copy();
    copy_ptr->translate(translate.x, translate.y);
    entt::entity res = cmd.add(copy_ptr);

    return res;
  }

  if constexpr (sizeof...(Args) > 0) {
    return copy_impl<Args...>(reg, e, cmd, translate);
  }
}

entt::entity copy(entt::registry& reg, entt::entity entity, layout::LayoutCmd& cmd, qlib::Vec2d translate) {
  return copy_impl<QLIB_CELL_ELEMENT_TYPE>(reg, entity, cmd, translate);
}

template <typename T, typename... Args>
entt::entity copy_impl(entt::registry& src_reg, entt::registry& new_reg, entt::entity e, layout::LayoutCmd& cmd, qlib::Vec2d translate,
                       qlib::Library::s_ptr new_lib) {
  auto ptr = src_reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    auto copy_ptr = (*ptr)->copy();

    if constexpr (std::is_same_v<T, qlib::Reference>) {
      if (&src_reg != &new_reg) {
        auto cells = (*ptr)->cell->deep_copy_logic();
        for (auto cell : cells) {
          new_lib->cells.push_back(cell);
        }
        copy_ptr->cell = cells[0];
        cmd.add_cell(copy_ptr->cell);
      }
    }

    copy_ptr->translate(translate.x, translate.y);
    entt::entity res = cmd.add(copy_ptr);

    return res;
  }

  if constexpr (sizeof...(Args) > 0) {
    return copy_impl<Args...>(src_reg, new_reg, e, cmd, translate, new_lib);
  }
}

entt::entity copy(entt::registry& src_reg, entt::registry& new_reg, entt::entity entity, layout::LayoutCmd& cmd, qlib::Vec2d translate,
                  qlib::Library::s_ptr new_lib) {
  return copy_impl<QLIB_CELL_ELEMENT_TYPE>(src_reg, new_reg, entity, cmd, translate, new_lib);
}

template <typename T, typename... Args>
void copy_impl(entt::registry& src_reg, entt::entity e, entt::registry& new_reg) {
  auto ptr = src_reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    auto copy_ptr = (*ptr)->copy();
    entt::entity res = new_reg.create();
    new_reg.emplace_or_replace<std::shared_ptr<T>>(e, copy_ptr);

    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    return copy_impl<Args...>(src_reg, e, new_reg);
  }
}

void copy(entt::registry& reg, entt::entity entity, entt::registry& new_reg) {
  return copy_impl<QLIB_CELL_ELEMENT_TYPE>(reg, entity, new_reg);
}

template <typename T, typename... Args>
void translate_impl(entt::registry& reg, entt::entity e, qlib::Vec2d vec) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    (*ptr)->translate(vec.x, vec.y);
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    translate_impl<Args...>(reg, e, vec);
  }
}

void translate(entt::registry& reg, entt::entity entity, qlib::Vec2d vec) {
  translate_impl<QLIB_CELL_ELEMENT_TYPE>(reg, entity, vec);
}

template <typename T, typename... Args>
void rotation_impl(entt::registry& reg, entt::entity e, qlib::Vec2d center, double rotation) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    (*ptr)->rotate(rotation, center);
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    rotation_impl<Args...>(reg, e, center, rotation);
  }
}

void rotation(entt::registry& reg, entt::entity entity, qlib::Vec2d center, double rotation) {
  rotation_impl<QLIB_CELL_ELEMENT_TYPE>(reg, entity, center, rotation);
}

template <typename T, typename... Args>
void rotation_implt(entt::registry& reg, entt::entity e, double rotation) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    auto box = (*ptr)->bounding_box();

    if constexpr (std::is_same_v<T, qlib::Reference>) {
      (*ptr)->rotation += rotation;
    } else if constexpr (std::is_same_v<T, qlib::Label>) {
      (*ptr)->rotation += rotation;
    } else if constexpr (std::is_same_v<T, qlib::Text>) {
      (*ptr)->rotation += rotation;
    } else if constexpr (std::is_same_v<T, qlib::Fan>) {
      (*ptr)->rotation += rotation;
    } else {
      qlib::Vec2d center = (box[0] + box[1]) / 2.0;
      (*ptr)->rotate(rotation, center);
    }

    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    rotation_implt<Args...>(reg, e, rotation);
  }
}

void rotation(entt::registry& reg, entt::entity entity, double rotation) {
  rotation_implt<QLIB_CELL_ELEMENT_TYPE>(reg, entity, rotation);
}

template <typename T, typename... Args>
void swap_impl(entt::registry& reg, entt::entity a, entt::entity b) {
  auto ptr_a = reg.try_get<std::shared_ptr<T>>(a);

  if (ptr_a != nullptr) {
    auto ptr_b = reg.try_get<std::shared_ptr<T>>(b);
    if (ptr_b != nullptr) {
      auto t = *ptr_b;

      (*ptr_b) = *ptr_a;
      (*ptr_a) = t;
    }
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    swap_impl<Args...>(reg, a, b);
  }
}

void swap(entt::registry& reg, entt::entity a, entt::entity b) {
  swap_impl<QLIB_CELL_ELEMENT_TYPE>(reg, a, b);
}

template <typename T, typename... Args>
void init(entt::entity e) {
  auto ptr_a = reg.try_get<std::shared_ptr<T>>(a);

  if (ptr_a != nullptr) {
    auto ptr_b = reg.try_get<std::shared_ptr<T>>(b);
    if (ptr_b != nullptr) {
      auto t = *ptr_b;

      (*ptr_b) = *ptr_a;
      (*ptr_a) = t;
    }
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    swap_impl<Args...>(reg, a, b);
  }
}

void CoreMap::init(entt::entity e) {}

void CoreMap::init(std::vector<entt::entity> es) {}

qlib::Vec2d getAngleAdsorbedPoint(qlib::Vec2d a, qlib::Vec2d b) {
  double real_x = b.x - a.x;
  double real_y = b.y - a.y;

  double xc = std::abs(real_x);
  double yc = std::abs(real_y);

  double c1 = std::abs(real_x - real_y) / std::sqrt(2);
  double c2 = std::abs(real_x + real_y) / std::sqrt(2);

  if (xc <= yc && xc <= c1 && xc <= c2) {
    return qlib::Vec2d(0, real_y) + a;
  }

  if (yc <= xc && yc <= c1 && yc <= c2) {
    return qlib::Vec2d(real_x, 0) + a;
  }

  if (c1 <= xc && c1 <= yc && c1 <= c2) {
    return qlib::Vec2d((real_x + real_y) / 2.0, (real_x + real_y) / 2.0) + a;
  }

  return qlib::Vec2d((real_x - real_y) / 2.0, -(real_x - real_y) / 2.0) + a;
}

qlib::Vec2d getAdsorbedPoint(ecs::Query query) {
  auto& axisModule = query.get_resource<axis::Axis>();
  auto& state = query.get_resource<state::State>();
  auto& absorbModels = query.get_resource<adsorb::AdsorbModel>();

  qlib::Vec2d pos;
  if (!state.isAdsorption()) {
    pos = {axisModule.real_x, axisModule.real_y};
  } else {
    pos = {axisModule.real_x, axisModule.real_y};
    // if (absorbModels.exist) {
    //   pos = absorbModels.point;
    // } else {
    //   pos = {axisModule.real_x, axisModule.real_y};
    // }
  }

  return pos;
}

qlib::Vec2dArray getInitBox() {
  qlib::Vec2dArray res(2);
  qlib::Vec2d& min = res[0];
  qlib::Vec2d& max = res[1];

  min.x = min.y = DBL_MAX;
  max.x = max.y = -DBL_MAX;

  return res;
}

template <typename T, typename... Args>
qlib::Vec2dArray getBox_impl(entt::registry& reg, entt::entity e) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    return (*ptr)->bounding_box();
  }

  if constexpr (sizeof...(Args) > 0) {
    return getBox_impl<Args...>(reg, e);
  } else {
    assert(0);
  }
}

qlib::Vec2dArray getBox(entt::registry& reg, entt::entity e) {
  return getBox_impl<QLIB_CELL_ELEMENT_TYPE>(reg, e);
}

template <typename T, typename... Args>
bool getBox_impl(entt::registry& reg, entt::entity e, qlib::Vec2dArray& res) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    res = (*ptr)->bounding_box();
    return true;
  }

  if constexpr (sizeof...(Args) > 0) {
    return getBox_impl<Args...>(reg, e, res);
  } else {
    return false;
  }
}

bool getBox(entt::registry& reg, entt::entity e, qlib::Vec2dArray& res) {
  return getBox_impl<QLIB_CELL_ELEMENT_TYPE>(reg, e, res);
}

qlib::Vec2dArray boxAndBox(qlib::Vec2dArray& box1, qlib::Vec2dArray& _box2) {
  auto box2 = _box2;

  if (box1[0].x < box2[0].x) {
    box2[0].x = box1[0].x;
  }
  if (box1[1].x > box2[1].x) {
    box2[1].x = box1[1].x;
  }

  if (box1[0].y < box2[0].y) {
    box2[0].y = box1[0].y;
  }
  if (box1[1].y > box2[1].y) {
    box2[1].y = box1[1].y;
  }

  return box2;
}

template <typename T, typename... Args>
void mirror_impl(entt::registry& reg, entt::entity entity, qlib::Vec2d p1, qlib::Vec2d p2) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(entity);

  if (ptr != nullptr) {
    (*ptr)->mirror(p1, p2);
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    return mirror_impl<Args...>(reg, entity, p1, p2);
  } else {
    // assert(0);
    return;
  }
}

void mirror(entt::registry& reg, entt::entity entity, qlib::Vec2d p1, qlib::Vec2d p2) {
  mirror_impl<QLIB_CELL_ELEMENT_TYPE>(reg, entity, p1, p2);
}

template <typename T, typename... Args>
qlib::Vec2dArray getParts_impl(entt::registry& reg, entt::entity entity, qlib::GeoPartType type, uint32_t id) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(entity);

  if (ptr != nullptr) {
    return (*ptr)->query_parts(type, id);
  }

  if constexpr (sizeof...(Args) > 0) {
    return getParts_impl<Args...>(reg, entity, type, id);
  } else {
    // assert(0);
  }
}

qlib::Vec2dArray getPartPoints(entt::registry& reg, entt::entity e, qlib::GeoPartType type, uint32_t id) {
  return getParts_impl<QLIB_CELL_ELEMENT_GEO_TYPE>(reg, e, type, id);
}

template <typename T, typename... Args>
uint32_t getLayer_impl(entt::registry& reg, entt::entity entity) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(entity);

  if (ptr != nullptr) {
    return (*ptr)->layer;
  }

  if constexpr (sizeof...(Args) > 0) {
    return getLayer_impl<Args...>(reg, entity);
  } else {
    assert(0);
  }
}

uint32_t getLayer(entt::registry& reg, entt::entity e) {
  return getLayer_impl<QLIB_CELL_ELEMENT_GEO_TYPE>(reg, e);
}

template <typename T, typename... Args>
bool entity_valid_impl(entt::registry& reg, entt::entity e) {
  auto ptr = reg.try_get<std::shared_ptr<T>>(e);

  if (ptr != nullptr) {
    return true;
  }

  if constexpr (sizeof...(Args) > 0) {
    return entity_valid_impl<Args...>(reg, e);
  } else {
    return false;
  }
}

template <typename T, typename... Args>
void getPolygons_impl(entt::registry& reg, entt::entity e, qlib::PolygonPtrArray& res) {
  auto ptr = reg.try_get<T>(e);

  if (ptr != nullptr) {
    qlib::PolygonPtrArray polygons;
    if constexpr (std::is_same_v<T, qlib::Reference::s_ptr>) {
      qlib::PolygonPtrArrayMap map;
      (*ptr)->get_polygons(true, true, -1, false, qlib::Tag(0, 0), map);
      auto it = map.begin();
      while (it != map.end()) {
        auto arr = it->second;
        polygons.insert(polygons.end(), arr.begin(), arr.end());
        it++;
      }
    } else if constexpr (std::is_same_v<T, qlib::Label::s_ptr>) {
      polygons = (*ptr)->to_polygons();
    } else if constexpr (std::is_same_v<T, qlib::Polygon::s_ptr>) {
      polygons.push_back(*ptr);
    } else if constexpr (std::is_same_v<T, qlib::KeyPoint::s_ptr>) {
      auto points_arr = (*ptr)->render_poly();
      for (auto& points : points_arr) {
        auto poly = qlib::Polygon::create();
        poly->points = points;
        poly->layer = (*ptr)->layer;
        polygons.push_back(poly);
      }
    } else if constexpr (std::is_same_v<T, qlib::ViewArea3D::s_ptr>) {

    } else if constexpr (std::is_same_v<T, qlib::SectionArea3D::s_ptr>) {

    } else {
      polygons.push_back((*ptr)->to_polygon());
    }

    if (!polygons.empty()) {
      res.insert(res.end(), polygons.begin(), polygons.end());
    }
    return;
  }

  if constexpr (sizeof...(Args) > 0) {
    getPolygons_impl<Args...>(reg, e, res);
    return;
  } else {
    return;
  }
}

qlib::PolygonPtrArray getPolygons(entt::registry& reg, entt::entity e) {
  qlib::PolygonPtrArray res;
  getPolygons_impl<QLIB_CELL_ELEMENT_PTR_TYPE>(reg, e, res);
  return res;
}

bool entity_valid(entt::registry& reg, entt::entity e) {
  return entity_valid_impl<QLIB_CELL_ELEMENT_TYPE>(reg, e);
}

uint32_t valid_entity_num(entt::registry& reg) {
  uint32_t res = 0;

  auto view = reg.view<entt::entity>();

  view.each([&reg, &res](entt::entity e) {
    if (entity_valid(reg, e)) {
      res++;
    }
  });

  return res;
}

}  // namespace qbe::utils